﻿using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using LowLevelGraphics;
using LowLevelGraphics.Filter;
using LowLevelGraphics.Extractors;
using LowLevelGraphics.Drawing;

namespace LowLevelGraphics
{
    //TODO: issues in ObjectExtractor:
    //
    //Recognition relevant:
    //
    //There is ONE big mistake:
    //If there's for example a big red letter or number, let's say an 8
    //then this gets recognized as two objects, THOUGH this algorithm should only
    //recognize ONE object then.
    //
    //Speed relevant:
    //
    //Momentarily almost all Ranges might be checked if they touch a new detected range,
    //if detected ranges of the previous lines are stored for direct access this
    //should result in a significant speed improveness (maybe realtime usable).
    //
    //Algorithm relevant:
    //
    //Maybe each pixel should be normalized, 
    //then be subtracted from the normalized value to get better edges
    //
    //Maybe edges should be used to improve this detection further.
    //
    //Results (now):
    //
    //Now this algorithms still suffers of the detection problem described in "recognition relevant".
    //So there is no real detection done yet. But if all ranges are added into a graph,
    //the pic is shown. Some pixel are staying black.
    //
    //
    //Ideas:
    //
    //1.) Let's test this algorithm wenn instead of relative object color detection
    //absolute color detection is used.
    //2.) What happens if we SKIP some pixels in the detection horizontally? (e.g. get 1st, skip 2nd, get 3rd pixel)
    //
    //Results (should be):
    //
    //1. stage: Results should at least be some little objects in random positions of the picture.
    //   (maybe this is all what's needed, which would perhaps be perfect)
    //
    //2. stage: should be some larger object detection in several position
    //
    //3. stage: complete recognition of objects.

    /// <summary>
    /// 
    /// </summary>
    public class ObjectExtractor : IExtractor
    {
        private List<BitmapObject> m_aObject = new List<BitmapObject>();
        private List<Range> m_aRange = new List<Range>();

        private UnsafeBitmap m_Bitmap = null;
        private int m_nHeight = -1;
        private int m_nWidth = -1;

        private bool m_bExcludeDominantColor = false;
        private EXPERIMENTAL1 m_Processing_mode = EXPERIMENTAL1.DEFAULT;
        private int m_nThreshold = 15;

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public ObjectExtractor(Bitmap _bitmap)
            : this(_bitmap, 3)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nThreshold">The _n threshold.</param>
        public ObjectExtractor(Bitmap _bitmap, int _nThreshold)
            : this(_bitmap, _nThreshold, false)
        {

        }

        /// <summary>
        /// Gets or sets the processing mode.
        /// </summary>
        /// <value>The processing mode.</value>
        public EXPERIMENTAL1 ProcessingMode
        {
            get { return m_Processing_mode; }
            set { m_Processing_mode = value; }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nThreshold">The _n threshold.</param>
        /// <param name="_bExcludeDominantColor">if set to <c>true</c> [_b exclude dominant color].</param>
        public ObjectExtractor(Bitmap _bitmap, int _nThreshold, bool _bExcludeDominantColor)
        {
            m_Bitmap = _bitmap;
            m_nThreshold = _nThreshold;
            m_bExcludeDominantColor = _bExcludeDominantColor;
            m_nWidth = _bitmap.Width;
            m_nHeight = _bitmap.Height;
        }

        /// <summary>
        /// Gets the bitmap.
        /// </summary>
        /// <value>The bitmap.</value>
        public Bitmap Bitmap
        {
            get { return m_Bitmap.InternalBitmap; }
        }

        /// <summary>
        /// Gets the ranges.
        /// </summary>
        /// <value>The ranges.</value>
        public List<Range> Ranges
        {
            get { return m_aRange; }
        }

        /// <summary>
        /// Extracts this instance.
        /// </summary>
        /// <returns></returns>
        public List<BitmapObject> Extract()
        {
            Color dominantColor = Color.Transparent;

            ColorCalculation calculation = null;

            if (m_Processing_mode != EXPERIMENTAL1.DEFAULT)
            {
                calculation = Definitions.ColorCalculationDictionary[m_Processing_mode];
            }

            //only calculate if we really need it...
            if (m_bExcludeDominantColor)
            {
                dominantColor = ColorFunctions.GetDominantColor(m_Bitmap);
            }

            UnsafeBitmap bitmap = m_Bitmap;

            Color color = Color.Transparent;
            Color colorPrev = Color.Transparent;

            bool bFound = false;
            int nStartX = -1;
            int nEndX = -1;
            int y = 0;
            int x = 0;
            int i = 0;

            colorPrev = bitmap.GetPixel(x, y);
            color = bitmap.GetPixel(x, y);

            List<BitmapObject> aBitmapObject = new List<BitmapObject>();

            Range currentRange;
            Range prevRange;
            Range range;

            List<Range> aRange = new List<Range>();

            double dDistance = -1.0f;

            int nSquareOfThreshold = m_nThreshold * m_nThreshold;

            Color colorTemp = Color.Transparent;
            for (y = 0; y < m_nHeight; y++)
            {
                bFound = false;

                //aRange.Clear();
                for (x = 0; x < m_nWidth; x++)
                {
                    colorPrev = color;
                    color = bitmap.GetPixel(x, y);

                    dDistance = ColorFunctions.DistanceSquared(colorPrev, color);

                    //This section will only be activated if ExcludeDominantColor is activated
                    if (color == dominantColor && m_bExcludeDominantColor)
                    {
                        //this is only done if we already started to detect an object
                        if (bFound)
                        {
                            nEndX = x;

                            colorTemp = bitmap.GetPixel(nStartX + (nEndX - nStartX / 2), y);
                            range = new Range(nStartX, nEndX, y, colorTemp);
                            //DrawRange(bitmap, range);
                            aRange.Add(range);
                        }
                        bFound = false;
                        continue;
                    }

                    if (bFound && dDistance > nSquareOfThreshold)
                    {
                        bFound = false;
                        nEndX = x;
                        colorTemp = bitmap.GetPixel(nStartX + (nEndX - nStartX / 2), y);
                        range = new Range(nStartX, nEndX, y, colorTemp);
                        //Debug.WriteLine(string.Format("{0}, {1}, {2}", range.StartX, range.EndX, range.Y));
                        //DrawRange(bitmap, range);
                        aRange.Add(range);
                        continue;
                    }
                    if (!bFound && dDistance <= nSquareOfThreshold)
                    {
                        bFound = true;
                        nStartX = x - 1;
                    }
                }
            }

            bFound = false;

            BitmapObject bitmapObject = null;

            for (i = 1; i < aRange.Count; i++)
            {
                if (i > aRange.Count) break;

                Range range1 = aRange[i - 1];
                Range range2 = aRange[i];

                if (range1.IntersectsOrTouches(range2))
                {
                    bFound = true;
                    bitmapObject = new BitmapObject();
                    bitmapObject.Ranges.Add(range2);
                }

                while (bFound)
                {
                    if (i > aRange.Count - 1)
                        break;
                    Range range1Temp = aRange[i - 1];
                    Range range2Temp = aRange[i];

                    if (!range2Temp.IntersectsOrTouches(range1Temp))
                    {
                        bFound = false;
                        break;
                    }
                    else
                    {
                        bFound = true;
                        bitmapObject.Ranges.Add(range2Temp);
                    }

                    i++;
                }
                aBitmapObject.Add(bitmapObject);

                bFound = false;

            }

            foreach (BitmapObject bitmapObjectTemp in aBitmapObject)
            {
                if (bitmapObjectTemp.Ranges.Count > 1)
                {
                    bitmapObjectTemp.CalculateContours();
                }
            }
            bitmap.UnlockImage();

            return aBitmapObject;
        }

        /// <summary>
        /// Draws the range.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_range">The _range.</param>
        private static void DrawRange(UnsafeBitmap _bitmap, Range _range)
        {
            for (int i = _range.StartX; i < _range.EndX; i++)
            {
                _bitmap.SetPixel(i, _range.Y, Color.Green);
            }
        }

        /// <summary>
        /// Calcs the distance.
        /// </summary>
        /// <param name="calculation">The calculation.</param>
        /// <param name="colorLast">The color last.</param>
        /// <param name="color">The color.</param>
        /// <returns></returns>
        private float CalcDistance(ColorCalculation calculation, ref Color colorLast, ref Color color)
        {
            float nDistance = m_Processing_mode == EXPERIMENTAL1.DEFAULT ?
                ColorFunctions.Distance(color, colorLast) :
                Math.Abs(calculation(color) - calculation(colorLast));
            return nDistance;
        }

        /// <summary>
        /// Gets the objects as list.
        /// </summary>
        /// <value>The objects as list.</value>
        public List<BitmapObject> ObjectsAsList
        {
            get { return m_aObject; }
        }

        /// <summary>
        /// Paints the object.
        /// </summary>
        /// <param name="_bitmapObject">The _bitmap object.</param>
        public void PaintObject(BitmapObject _bitmapObject)
        {
            PaintObject(null, _bitmapObject, Definitions.White, true, true, true);
        }

        /// <summary>
        /// Paints the object.
        /// </summary>
        /// <param name="g">The g.</param>
        /// <param name="_bitmapObject">The _bitmap object.</param>
        /// <param name="_color">The _color.</param>
        /// <param name="_bFill">if set to <c>true</c> [_b fill].</param>
        /// <param name="_bCloseFirstAndLastLines">if set to <c>true</c> [_b close first and last lines].</param>
        /// <param name="_bDrawLines">if set to <c>true</c> [_b draw lines].</param>
        public void PaintObject(Graphics g, BitmapObject _bitmapObject, Color? _color, bool _bFill, bool _bCloseFirstAndLastLines, bool _bDrawLines)
        {
            if (g == null)
            {
                g = Graphics.FromImage(m_Bitmap.InternalBitmap);
            }

            Color? color = _color;
            if (_color.HasValue)
            {
                color = _color;
            }
            else
            {
                color = _bitmapObject.Ranges[0].Color;
                color = Color.FromArgb(255 - color.Value.R, 255 - color.Value.G, 255 - color.Value.B);
            }

            if (_bFill)
            {
                foreach (Range range in _bitmapObject.Ranges)
                {
                    g.DrawLine(new Pen(new SolidBrush(range.Color), 1), range.StartX, range.Y, range.EndX, range.Y);
                }
            }
            else
            {
                if (_bCloseFirstAndLastLines)
                {
                    if (_bitmapObject.Points.Count >= 2)
                    {
                        Point ptFirstStart = _bitmapObject.Points[0];
                        Point ptFirstEnd = _bitmapObject.Points[1];
                        Point ptLastStart = _bitmapObject.Points[_bitmapObject.Points.Count - 2];
                        Point ptLastEnd = _bitmapObject.Points[_bitmapObject.Points.Count - 1];

                        g.DrawLine(new Pen(new SolidBrush(color.Value), 1), ptFirstStart.X, ptFirstStart.Y, ptFirstEnd.X, ptFirstEnd.Y);
                        g.DrawLine(new Pen(new SolidBrush(color.Value), 1), ptLastStart.X, ptLastStart.Y, ptLastEnd.X, ptLastEnd.Y);
                    }
                }

                foreach (Point point in _bitmapObject.Points)
                {
                    g.FillRectangle(new SolidBrush(color.Value), new Rectangle(point.X, point.Y, 1, 1));
                }
            }
        }
    }
}
